package com.gcloud.mesh.distributedSchedule.cluster;

import java.util.HashMap;

//import com.gcloud.mesh.Application;
import com.gcloud.mesh.distributedSchedule.AppStatus;
import com.gcloud.mesh.distributedSchedule.ClusterAvailableResource;
import com.gcloud.mesh.distributedSchedule.IApplication;
import com.gcloud.mesh.distributedSchedule.ICluster;
import com.gcloud.mesh.distributedSchedule.application.Application;
import com.gcloud.mesh.ml.LinearRegression;
import com.gcloud.mesh.ml.TrainingSet;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class CommonCluster implements ICluster {

    // idc室内室外温度，对效能有影响
    private double outdoorTemp, indoorTemp;

    private int totalCpuCores, restCpuCores;
    private double totalMemSpace, restMemSpace;
    private int totalStorageSpace, restStorageSpace;
    private int totalBandWidth, restBandWidth;

    // 电力单价
    private double kwhCost;
    // 数据中心最大输入功率
    private int maxInputPowerKwh;
    // 安全功率
    private double securePowerRate = 0.7;

    // CPU and memory performance parameters of single node, presents the performance.
    private int cpuCoreNum;                  // single (socket) cpu core number
    private float highestCpuFrequencyG;
    private int L2CacheSizeKB;
    private int TDP;
    private double nodeMemorySizeG;
    private float memorySpeedG;

    // last instant power reported.
    private int allServerPower;
    private int refrigerateSystemPower;
    private int powerSupplierLostPower;   // 电源系统(ups或者power manager)损失(消耗)的功率，通常电源系统的功率系数只有0.8到0.9
    private int otherDevicePower;

    // 集群id
    private String id;

    // key value to represent energy efficiency of this cluster(data center).
    // higher is better.
    private int energyEfficiencyValue;

    // L2缓存转换为性能值的系数，暂定0.7
    private static final double L2CachePerformanceTransRatio = 0.7;
    private static final double MemorySizePerformanceTransRatio = 0.3;

    // 高效率负载系数，达到了这个负载系数就认为集群达到了高效率
    private double EfficientLoadRate = 0.7;

    // PUE = 总功耗/计算功耗，受硬件条件、环境及当前计算负载决定。
    // PUE在当前负载下的值，与迁移负载后的值是不同的，而我们计算IDC集群的准确能效值，依赖的是迁移后的PUE值。
    // 而我们只有当前未迁移负载的PUE值（通过动环监控数据可以计算出来），这会导致能效值计算的误差，因此，
    // 我们需要预知迁移负载后的PUE值，这个任务交给机器学习算法.
    // 如果省略对迁移后PUE的预测，则可以直接使用当前负载下的PUE值代替，精度略差.
    //
    private double PUE = 1.3;

    //
    private TrainingSet ts = new TrainingSet();

    // 线性回归模型
    private LinearRegression lr = new LinearRegression();

    // 让它可以自动根据CPU型号调整；这个参数对系统的影响其实体现在PUE参数里，所以只需要做到PUE参数的自动调整即可；
    // 负载系数，根据CPU的性能功率比，性能（负载）越高，性能功率比值越大，根据经验，
    // 这个系数设定在1到1.4
    private double loadCoefficient = 1.0;

    private HashMap<String, IApplication> appMap = null;

    public HashMap<String, IApplication> getAppMap() {
        return this.appMap;
    }

    public int getInstantPowerKWH() {
        return this.allServerPower + this.refrigerateSystemPower + this.powerSupplierLostPower + this.otherDevicePower;
    }

    public void setSecurePowerRate(double rate) throws Exception {
        if (rate <= 0 || rate >= 1.0) {
            throw new Exception("安全功率比率超出正常范围:" + rate);
        }
        this.securePowerRate = rate;
    }

    public double getSecurePowerRate() {
        return this.securePowerRate;
    }

    /**
     * @param id 集群id，必须全局唯一
     * @param initialCapacity  集群应用数量限制，没有限制则0
     */
    public CommonCluster(String id, int initialCapacity) {
        if (initialCapacity > 0)
            this.appMap = new HashMap<>(initialCapacity);
        else
            this.appMap = new HashMap<>();
        this.id = id;
    }

    /**
     * 集群的计算资源设置
     */
    @Override
    public void init(int cpuCores, double memSpaceG, int storageSpaceG, int bandWidthKBPS, double kwhCost, int maxInputPowerKwh) {
        this.totalBandWidth = bandWidthKBPS;
        this.totalCpuCores = cpuCores;
        this.totalMemSpace = memSpaceG;
        this.totalStorageSpace = storageSpaceG;
        this.kwhCost = kwhCost;
        this.maxInputPowerKwh = maxInputPowerKwh;
    }

    public int getMaxInputPowerKwh() {
        return this.maxInputPowerKwh;
    }
    public void setEfficientLoadRate(double loadRate) throws Exception {
        if (loadRate > 1.0 || loadRate <= 0) {
            throw new Exception("loadRate is out of range, which is should setup between 0 to 1.0.");
        }
        if (loadRate > 0.8) {
            // TODO: warn log.
        }
        this.EfficientLoadRate = loadRate;
    }

    public double getEfficientLoadRate() {
        return this.EfficientLoadRate;
    }

    // 没有考虑负载系数，CPU负载越高，能效越高;因为实时PUE其实已经囊括和反映了负载系数对能效的影响
    private double calEnergyEffi() {
        double performance = this.cpuCoreNum *
                             this.highestCpuFrequencyG *
                             (this.L2CacheSizeKB / 128) * L2CachePerformanceTransRatio *
                             (this.totalMemSpace / 32) * MemorySizePerformanceTransRatio * this.memorySpeedG;

        // * 100 to amplify performance value, no special meaning.
        return performance * 100 / (this.TDP); //   / this.PUE;
    }

    @Override
    public void updateInstantPower(int serverPowerWatt, int refrigerationPowerWatt, int psuLossPowerWatt, int devicePowerWatt) {
        this.allServerPower = serverPowerWatt;
        this.refrigerateSystemPower = refrigerationPowerWatt;
        this.powerSupplierLostPower = psuLossPowerWatt;
        this.otherDevicePower = devicePowerWatt;

        this.PUE = (serverPowerWatt + refrigerationPowerWatt + psuLossPowerWatt + devicePowerWatt) / (serverPowerWatt + devicePowerWatt);
    }

    boolean needTraining = false;

    @Override
    public void updateInstantRestResource(int cpuCores, double memSpaceG, int storageSpaceG, int bandWidthKBPS) {
        this.restBandWidth = bandWidthKBPS;
        this.restCpuCores = cpuCores;
        this.restMemSpace = memSpaceG;
        this.restStorageSpace = storageSpaceG;

        if (!this.lr.trained()) {
            // TODO: 目前初步使用这些参数来训练模型，效果不佳可以调整
            double clsInstantCpuLoad = 100.0 * (this.totalCpuCores - cpuCores) / this.totalCpuCores;
            double clsInstantMemLoad = 100.0 * (this.totalMemSpace - memSpaceG) / this.totalMemSpace;
            double clsInstantStorageLoad = 100.0 * (this.totalStorageSpace - storageSpaceG) / this.totalStorageSpace;
            double clsInstantBandLoad = 100.0 * (this.totalBandWidth - bandWidthKBPS) / this.totalBandWidth;

            // 注意训练参数顺序不能搞乱
            double[] trainData = {clsInstantCpuLoad,
                                  clsInstantMemLoad,
                                  clsInstantStorageLoad,
                                  clsInstantBandLoad,
                                  this.indoorTemp,
                                  this.outdoorTemp};

            this.preprocessTrainData(this.PUE, trainData);
        }
    }

    // 训练数据需要数量和质量，质量指的是要分布均匀
    // TODO: cpu load 最好要均匀分布在0-100的范围内，现在先满足数量吧
    private void preprocessTrainData(double y, double[] x) {
        for (int i = 0; i < 4; i++) {
            if (x[i] < 0 || x[i] > 100) {
                log.warn("非法数据，此值是百分百，范围在0-100以内,放弃此训练数据。");
                return;
            }
        }
        this.ts.add(y, x);

        // TODO: 6 parameters to be trained by 120 cases
        if (this.ts.dataList.size() >= 120) {
            this.lr.setTrainingData(ts);
            try {
                this.lr.training();
            }
            catch (Exception e) {
                log.error("训练遇到异常：" + e.toString());
            }
        }
    }

    @Override
    public void setupPerformanceInfo(int cpuCoreNum, float highestCpuFrequencyG, int L2CacheSizeKB,
                                     int cpuTDP, double memorySizeG, float memorySpeedG) {
        this.cpuCoreNum = cpuCoreNum;
        this.highestCpuFrequencyG = highestCpuFrequencyG;
        this.L2CacheSizeKB = L2CacheSizeKB;
        this.TDP = cpuTDP;
        this.nodeMemorySizeG = memorySizeG;
        this.memorySpeedG = memorySpeedG;
    }

    @Override
    public void addApplication(IApplication app) throws Exception {
        if (this.appMap.get(app.getId()) == null) {
            app.setClusterId(this.id);
            this.appMap.put(app.getId(), app);
        }
        else {
            throw new Exception(String.format("app with id [%s] already exist in cluster.", app.getId()));
        }
    }

    @Override
    public void rmApplication(String appId) {
        this.appMap.remove(appId);
    }

    @Override
    public String getId() {
        return this.id;
    }

    /**
     * 如果app为null，则返回当前负载下的能效值，
     * 如果不为null并且ML模型已经训练好，则返回迁移入应用后本集群的能效值
     * @param app 要迁移进入此集群的应用，null表示不指定
     * @return 返回能效值
     */
    @Override
    public int getEfficientValue(IApplication app) {

        if (app == null || !this.lr.trained()) {
            // return current energy efficient value.
            this.energyEfficiencyValue = (int) Math.round(this.calEnergyEffi() / this.PUE);
            return this.energyEfficiencyValue;
        }
        else {
            // return predicted energy efficient value.
            // because what we really need is the efficiency status after app moving in.
            // so we predicting the status of moved with ML model.
            AppStatus as = app.getStatus();
            TrainingSet.Data trs = new TrainingSet.Data();

            // 新的负载 = 现在负载 + 应用的负载
            // cpu  mem  storage  bandwidth indoortemp  outdoortemp
            double[] x = {100.0 * (this.totalCpuCores - this.restCpuCores + as.cpuCores) / this.totalCpuCores,
                           100.0 * (this.totalMemSpace - this.restMemSpace + as.memSpaceG) / this.totalMemSpace,
                           100.0 * (this.totalStorageSpace - this.restStorageSpace + as.storageSpaceG) / this.totalStorageSpace,
                           100.0 * (this.totalBandWidth - this.restBandWidth + as.bandWidthKBPS) / this.totalBandWidth,
                            this.indoorTemp,
                            this.outdoorTemp};
            trs.x = x;
            trs = this.lr.predict(trs); // predicted PUE
            return (int) Math.round(this.calEnergyEffi() / trs.y);
        }
    }

    @Override
    public ClusterAvailableResource getAvailResource() {
        return new ClusterAvailableResource(this.restCpuCores, this.restMemSpace,
                this.restStorageSpace, this.restBandWidth);
    }

    /**
     * 评估运行这些应用后集群剩下的资源
     * @param appList
     * @return
     */
    public ClusterAvailableResource getAvailResource(Application[] appList) {
        int restCpuCores = 0;
        double restMemG = 0.0;
        int restBandWidthKBPS = 0;
        int restStorageSpaceG = 0;

        for (Application app : appList) {
            // 这个app本身就在这个集群，已经统计过，忽略
            if (this.appMap.containsKey(app.getAppId())) continue;

            restCpuCores += this.restBandWidth - app.getCpuCost();
            restMemG += this.restMemSpace - app.getMemSpaceCost();
            restBandWidthKBPS += this.restBandWidth - app.getBandWidtdKBPS();
            restStorageSpaceG += this.restStorageSpace - app.getStorageSpaceCost();
        }

        return new ClusterAvailableResource(restCpuCores, restMemG, restStorageSpaceG, restBandWidthKBPS);
    }

    public ClusterAvailableResource getTotalResource() {
        return new ClusterAvailableResource(this.totalCpuCores, this.totalMemSpace, this.totalStorageSpace, this.totalBandWidth);
    }

    /**
     * 有些cluster的动环没有提供各种功耗，直接提供PUE，所以就在这里计算集群的能效值了
     * @param pueValue
     */
    @Override
    public void updatePue(double pueValue) {
        this.PUE = pueValue;
    }

    @Override
    public void updateEnvTemperature(double outdoorTemp, double indoorTemp) {
        this.outdoorTemp = outdoorTemp;
        this.indoorTemp = indoorTemp;
    }

    public int getNodeCpuCoreNum() {
        return this.cpuCoreNum;
    }

    public double getNodeMemSizeG() {
        return this.nodeMemorySizeG;
    }

    public float getNodeCpuHighestFrequency() {
        return this.highestCpuFrequencyG;
    }

    public int getL2CacheSizeKB() {
        return this.L2CacheSizeKB;
    }
}
